﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Data.Common;
using SmartLib.Attributes;

namespace SmartLib.Dal.Sql
{
    internal class QueryExpressionVistor : ExpressionVisitor
    {
        private StringBuilder _sqlBuilder;
        private Expression _expression;
        private DbCommand _cmd;
        private Func<string, string> BuildParameterName;
        private string _prefix;
        private int _parameterIndex = 0;

        internal QueryExpressionVistor(Expression expression)
        {
            _expression = expression;
            _prefix = string.Empty;
        }

        private void VisitBinaryLeaf(Expression leaf)
        {
            if (leaf.NodeType == ExpressionType.Constant ||
                leaf.NodeType == ExpressionType.MemberAccess)
            {
                Visit(leaf);
            }
            else
            {
                _sqlBuilder.Append(" ( ");
                Visit(leaf);
                _sqlBuilder.Append(" ) ");
            }
        }

        private void AddParameter(string memberName, object value)
        {
            if (_cmd != null)
            {
                DbParameter parameter = _cmd.CreateParameter();
                parameter.ParameterName = memberName;
                parameter.Value = value;
                _cmd.Parameters.Add(parameter);
            }
        }

        private string GetMemberName(MemberExpression node)
        {
            string memberName = string.Empty;

            TableColumnAttribute[] attrs = (TableColumnAttribute[])node.Member.GetCustomAttributes(typeof(TableColumnAttribute), false);
            if (attrs != null && attrs.Length > 0)
            {
                memberName = attrs[0].ColumnName;
            }

            if (string.IsNullOrEmpty(memberName))
            {
                memberName = node.Member.Name;
            }

            if (!string.IsNullOrEmpty(_prefix))
            {
                memberName = _prefix + "." + memberName;
            }

            return memberName;
        }

        private bool BuildParameters(BinaryExpression node)
        {
            string parameterName = string.Empty;
            string memberName = string.Empty;
            object value = null;

            if (node.Left.NodeType == ExpressionType.MemberAccess &&
                node.Right.NodeType == ExpressionType.Constant)
            {
                memberName = GetMemberName((MemberExpression)node.Left);
                parameterName = BuildParameterName("p" + _parameterIndex.ToString());
                value = ((ConstantExpression)node.Right).Value;

                _sqlBuilder.Append(memberName);
                _sqlBuilder.Append(SqlStringHelper.ExpressionType2SqlString(node.NodeType));
                _sqlBuilder.Append(parameterName);

                AddParameter("p" + _parameterIndex, value);

                _parameterIndex++;

                return true;
            }
            else if (node.Right.NodeType == ExpressionType.MemberAccess &&
                node.Left.NodeType == ExpressionType.Constant)
            {
                memberName = GetMemberName((MemberExpression)node.Right);
                parameterName = BuildParameterName("p" + _parameterIndex);
                value = ((ConstantExpression)node.Left).Value;

                _sqlBuilder.Append(parameterName);
                _sqlBuilder.Append(SqlStringHelper.ExpressionType2SqlString(node.NodeType));
                _sqlBuilder.Append(memberName);
                AddParameter("p" + _parameterIndex, value);
                _parameterIndex++;
                return true;
            }

            return false;
        }

        private void VisitLikeMethod(MethodCallExpression node)
        {
            string value = string.Empty;
            string methodName = node.Method.Name.ToLower();
            if (methodName == "startswith")
            {
                value = ((ConstantExpression)node.Arguments[0]).Value + "%";
            }
            else if (methodName == "endswith")
            {
                value = "%" + ((ConstantExpression)node.Arguments[0]).Value;
            }
            else
            {
                value = "%" + ((ConstantExpression)node.Arguments[0]).Value + "%";
            }

            MemberExpression memberExpression = ((MemberExpression)node.Object);
            string memberName = GetMemberName(memberExpression);

            _sqlBuilder.Append(memberName);
            _sqlBuilder.AppendFormat(" like ");
            _sqlBuilder.Append(BuildParameterName("p" + _parameterIndex));

            AddParameter("p" + _parameterIndex, value);

            _parameterIndex++;
        }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            string methodName = node.Method.Name.ToLower();

            if (methodName == "where")
            {
                Visit(node.Arguments[1]);
            }
            else if (methodName == "startswith" || methodName == "endswith" ||
                methodName == "contains")
            {
                VisitLikeMethod(node);
            }
            else
            {
                throw new Exception(String.Format("暂不支持的方法{0}", node.Method.Name));
            }
            return node;
        }

        protected override Expression VisitBinary(BinaryExpression node)
        {
            if (!BuildParameters(node))
            {

                VisitBinaryLeaf(node.Left);

                string oper = SqlStringHelper.ExpressionType2SqlString(node.NodeType);
                if (string.IsNullOrEmpty(oper)) throw new Exception("暂不支持的操作符");
                _sqlBuilder.Append(oper);

                VisitBinaryLeaf(node.Right);
            }
            return node;
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            _sqlBuilder.Append(GetMemberName(node));

            return node;
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            _sqlBuilder.Append(node.Value);

            return base.VisitConstant(node);
        }

        public string BuildSql(DbCommand cmd)
        {
            _cmd = cmd;
            _parameterIndex = 0;
            if (_sqlBuilder == null) _sqlBuilder = new StringBuilder();
            _sqlBuilder.Clear();

            if (_expression != null)
            {
                Visit(_expression);
            }

            return _sqlBuilder.ToString();
        }

        public string BuildSqlWithPrefix(DbCommand cmd, string prefix)
        {
            _prefix = prefix;
            return BuildSql(cmd);
        }

        public string BuildSql(DbCommand cmd, string parameterToken)
        {
            BuildParameterName = (memberName) => memberName.Insert(0, parameterToken);

            return BuildSql(cmd);
        }

        public string BuildSqlWithPrefix(DbCommand cmd, string parameterToken, string prefix)
        {
            _prefix = prefix;
            return BuildSql(cmd, parameterToken);
        }

        public string BuildSql(DbCommand cmd, Func<string, string> buildParameter)
        {
            BuildParameterName = buildParameter;

            return BuildSql(cmd);
        }

        public string BuildSqlWithPrefix(DbCommand cmd, Func<string, string> buildParameter, string prefix)
        {
            _prefix = prefix;
            return BuildSql(cmd, buildParameter);
        }
    }
}
